package com.mrtaohuan.render.freemarker;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import lombok.extern.java.Log;

import com.mrtaohuan.servlet.listener.GuiceConfigListener;

import freemarker.core.TemplateElement;
import freemarker.template.Configuration;
import freemarker.template.Template;

@Log
public class Templates {

	/**
	 * Main template {@link Configuration configuration}.
	 */
	public static final Configuration CFG = new Configuration();

	/**
	 * Template cache.
	 * 
	 * <p>
	 * &lt;templateDirName/templateName, template&gt;
	 * </p>
	 */
	public static final Map<String, Template> CACHE = new HashMap<String, Template>();

	/**
	 * Enables the {@linkplain #CACHE cache}? Default to {@code true}.
	 */
	private static boolean cacheEnabled = false;

	private Templates() {
	}

	static {
		CFG.setDefaultEncoding("UTF-8");
	}

	/**
	 * Determines whether exists a variable specified by the given expression in
	 * the specified template.
	 * 
	 * @param template
	 *            the specified template
	 * @param expression
	 *            the given expression, for example, "${aVariable}",
	 *            "&lt;#list recentComments as comment&gt;"
	 * @return {@code true} if it exists, returns {@code false} otherwise
	 */
	public static boolean hasExpression(final Template template,
			final String expression) {
		final TemplateElement rootTreeNode = template.getRootTreeNode();

		return hasExpression(template, expression, rootTreeNode);
	}

	/**
	 * Determines whether the specified expression exists in the specified
	 * element (includes its children) of the specified template.
	 * 
	 * @param template
	 *            the specified template
	 * @param expression
	 *            the specified expression
	 * @param templateElement
	 *            the specified element
	 * @return {@code true} if it exists, returns {@code false} otherwise
	 */
	private static boolean hasExpression(final Template template,
			final String expression, final TemplateElement templateElement) {
		final String canonicalForm = templateElement.getCanonicalForm();

		if (canonicalForm.startsWith(expression)) {
			log.log(Level.FINEST,
					"Template has expression[nodeName={0}, expression={1}]",
					new Object[] { templateElement.getNodeName(), expression });

			return true;
		}

		@SuppressWarnings("unchecked")
		final Enumeration<TemplateElement> children = templateElement
				.children();

		while (children.hasMoreElements()) {
			final TemplateElement nextElement = children.nextElement();

			if (hasExpression(template, expression, nextElement)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Enables or disables the template cache.
	 * 
	 * @param enabled
	 *            {@code true} to enable, disable otherwise
	 */
	public static void enableCache(final boolean enabled) {
		cacheEnabled = enabled;
	}

	/**
	 * Gets a FreeMarker {@linkplain Template template} with the specified
	 * template directory name and template name.
	 * 
	 * @param templateDirName
	 *            the specified template directory name
	 * @param templateName
	 *            the specified template name
	 * @return a template, returns {@code null} if not found
	 */
	public static Template getTemplate(final String templateDirName,
			final String templateName) {
		Template ret = null;

		try {
			if (cacheEnabled) {
				ret = CACHE
						.get(templateDirName + File.separator + templateName);
			}

			if (null != ret) {
				log.log(Level.FINEST,
						"Got template[templateName={0}] from cache",
						templateName);
				return ret;
			}
			File file = new File(GuiceConfigListener.getWebRoot() + "templates");
			CFG.setDirectoryForTemplateLoading(file);
			ret = CFG.getTemplate(templateName);

			if (cacheEnabled) {
				CACHE.put(templateDirName + File.separator + templateName, ret);
				log.log(Level.FINEST,
						"Got template[templateName={0}], then put it into template cache",
						templateName);
			}

			return ret;
		} catch (final IOException e) {
			log.log(Level.FINEST, "Gets template[name={0}] failed: [{1}]",
					new Object[] { templateName, e.getMessage() });
			return null;
		}
	}
}
